Skip to content

fix(tui): resolve terminal theme mode from every signal instead of guessing dark - #1152

Open
sahrizvi wants to merge 5 commits into
mainfrom
fix/tui-terminal-readability
Open

fix(tui): resolve terminal theme mode from every signal instead of guessing dark#1152
sahrizvi wants to merge 5 commits into
mainfrom
fix/tui-terminal-readability

Conversation

@sahrizvi

@sahrizvi sahrizvi commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #736
Refs #809

Closes for #736, Refs for #809 — the difference is deliberate and explained in the scope note. Everything else this description mentions is named to put it out of scope, not to claim it.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

The bug. Code output renders in near-white on a light terminal — readable when the session is opened in VS Code, not in the terminal itself. This is the third report of it: #617#704#736.

Why the previous two fixes did not hold. Both adjusted colour values. The defect is not a palette; it is that the mode-resolution chain in app.tsx ended in a hardcoded guess:

const mode = envMode === "light" ? "light" : ((await renderer.waitForThemeMode(1000)) ?? "dark")

Apple Terminal — named in #736's metadata next to "macOS Appearance: Light" — sets no COLORFGBG and does not reliably answer the OSC 11 background query. With both signals absent the chain returns "dark", so a light-background user gets the dark palette no matter how well that palette is tuned. No colour change could close it.

Two layers are fixed.

Startup detection. resolveInitialMode() now encodes the whole chain as one pure function, ordered by how well each signal describes this terminal window: the OSC 11 reply, then COLORFGBG, then OS appearance, then dark as a genuine last resort. OSC outranks COLORFGBG because the env var is inherited and survives ssh, tmux, sudo and profile changes; COLORFGBG now only shortens the OSC wait to 250ms, which keeps the startup win from #704 without letting a stale value beat a live answer. detectSystemAppearance() supplies the signal that was missing — macOS leaves AppleInterfaceStyle unset in light mode, so a defaults exit saying the key does not exist is the light answer, while EACCES, ENOENT, a signal or a timeout mean unknown. It runs /usr/bin/defaults so a stray binary on PATH cannot answer, and skips the probe entirely over ssh (where the appearance belongs to the remote host) and in CI.

Direct-run renderer. resolveRunTheme returned a hardcoded dark fallback on both failure exits, so a light terminal whose palette query failed got dark panels. That fallback's text prefers the terminal's own default foreground, which on a light terminal resolves to black — black text over a hardcoded #0f172a panel is literally dark text in a dark box. The fallback is now built per mode and memoized; the dark instance is unchanged by identity, so callers comparing it with toBe are unaffected.

How did you verify your code works?

Mutation, not a green suite. Seven mutants were each confirmed to fail a test:

Mutation Caught by
Restore the hardcoded dark fallback the #736-shape test
COLORFGBG outranks OSC again precedence test
Drop the ssh guard ssh probe test
Drop the CI guard CI probe test
Use a relative defaults absolute-path test
Any defaults failure means light failure-classification tests
Direct-run fallback always dark / light panel still dark direct-run fallback tests

execFile is injected and the probe tests use a spy, so "does not spawn" is asserted rather than assumed.

Suites: 288 tui, 187 cli/run, 9 direct-run theme, typecheck clean. The one failing tui test (formats session continuation summary) fails identically on main.

Screenshots / recordings

Not a UI-layout change; the observable difference is text colour on a light terminal.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Scope note. An audit of the whole colour-legibility area found the original grouping of these reports was wrong, so this claims only what the code supports:

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved startup theme detection using terminal settings, environment preferences, and macOS appearance.
    • Added light and dark fallback themes when terminal theme detection is unavailable.
    • Detection now prioritizes terminal signals, then system appearance, before defaulting to dark mode.
  • Bug Fixes

    • Light-mode terminals now receive a genuinely lighter fallback palette.
  • Tests

    • Added coverage for terminal, OS, timeout, SSH, CI, and fallback theme detection scenarios.

sahrizvi and others added 3 commits August 21, 2026 04:32
…ssing dark

Third attempt at the same defect (#617#704#736): code rendered in
near-white on a light terminal, readable in VS Code but not in the terminal
itself. The first two fixes adjusted colour values, which is why neither held.

The actual defect is in `app.tsx`, where the mode-resolution chain ended in a
hardcoded fallback:

    const envMode = detectModeFromCOLORFGBG(process.env.COLORFGBG)
    const mode = envMode === "light" ? "light" : ((await renderer.waitForThemeMode(1000)) ?? "dark")

Apple Terminal — the client named in #736's metadata, alongside "macOS
Appearance: Light" — sets no `COLORFGBG` and does not reliably answer the OSC 11
background query. Both signals are therefore absent, the chain returns "dark",
and a light-background user gets the dark palette no matter how its colours are
tuned. That is not a palette bug, so palette fixes could not close it.

Changes:

- `resolveInitialMode()` encodes the whole chain as one pure function, ordered
  by how well each signal describes *this terminal window*: COLORFGBG, then the
  OSC 11 reply, then OS appearance, then dark as a genuine last resort. A
  dark-profile terminal under a light system theme stays dark.
- `detectSystemAppearance()` adds the signal that was missing. macOS sets
  `AppleInterfaceStyle` to "Dark" in dark mode and leaves it *unset* in light
  mode, so `defaults` exiting non-zero is the light answer rather than a
  failure; only ENOENT or a timeout is treated as "unknown". Every report of
  this bug came from darwin.
- The call site now honours a dark `COLORFGBG` too. It previously kept only
  "light", so a terminal that had already reported a dark background still paid
  the full one-second OSC timeout before agreeing with it.

`detectModeFromCOLORFGBG` carried a comment saying it was "extracted from
app.tsx for direct test coverage (#704)" but had no tests at all. It does now.

Verified by mutation rather than by the suite going green: restoring the old
hardcoded fallback fails the test named for #736, discarding a dark COLORFGBG
fails the precedence test, and letting OS appearance outrank the terminal's own
background fails two more.

Scope note: this closes the colour-mode family. #404 (garbled ASCII logo), #609
(malformed layout) and #737 (unexpected CJK glyphs) were grouped with it during
triage, but they are glyph-width and encoding problems rather than colour, and
need separate work. #809 (dark text on a dark box) is plausibly the same
misdetection, but the report carries no terminal details, so it is referenced
rather than closed.

Tests: 13 new, 280 tui pass (1 pre-existing failure unrelated, identical on main).

Closes #736
Refs #809

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… fallback assuming dark

Reworked after a codex audit of the whole colour-legibility class, which found
the first attempt was aimed at the less important of two layers and that several
of its tests proved less than their names claimed.

## Direct-run renderer (the layer the audit ranked higher)

`resolveRunTheme` returned a hardcoded dark `RUN_THEME_FALLBACK` on both failure
exits, so a light terminal whose palette query failed got dark panels. Worse,
that fallback's `text` prefers the terminal's *own* default foreground, which on
a light terminal resolves to black — a black foreground over a hardcoded
`#0f172a` panel is literally dark text in a dark box, the symptom reported in
#809.

The fallback is now built per mode and memoized, and both exits resolve a mode
first. The dark instance is still the same object, so callers comparing it by
identity are unaffected.

## Startup detection

- Precedence corrected. OSC 11 describes *this* window right now; `COLORFGBG` is
  inherited and survives ssh, tmux, sudo and profile changes. The previous
  ordering let a stale env var override a live answer. COLORFGBG now only
  shortens the OSC wait (250ms instead of 1s), which keeps #704's startup win
  without trading away correctness.
- The appearance probe no longer reports "light" for every failure. macOS leaves
  `AppleInterfaceStyle` unset in light mode and `defaults` says so explicitly;
  that diagnostic is the light answer, while EACCES, EMFILE, ENOENT, a signal or
  a timeout mean unknown. Guessing light on those produces the inverse of the
  bug being fixed.
- It invokes `/usr/bin/defaults`, so a different `defaults` earlier on PATH
  cannot answer a question about macOS appearance.
- It does not run over ssh, where the appearance belongs to the remote host
  rather than the terminal the user is looking at, nor in CI.

## Tests

The audit named six tests that overclaimed. `execFile` is now injectable and the
probe tests use a spy, so "does not spawn" is asserted rather than assumed, and
every failure branch is driven directly. The regression test is renamed for the
shape it actually covers instead of implying end-to-end coverage it does not
have.

Seven mutants were confirmed to fail: old precedence, missing ssh guard, missing
CI guard, relative `defaults`, any-failure-means-light, always-dark direct-run
fallback, and a light fallback whose panel is still dark.

## Scope

Claims only what the code supports. The audit found #617 was missing Markdown
`fg` and code-block background (fixed separately in 5ae5b79), #704 bundled
three changes with no way to attribute the fix, and #404/#609/#737/#116 are
glyph-width, encoding or layout problems rather than colour. #736 is the
best-supported colour-mode case but remains conditional, so it is referenced,
not closed.

Tests: 17 detection, 9 direct-run theme, 284 tui, 187 cli/run. Typecheck clean.

Refs #736
Refs #809

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@github-actions

Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c4a549c2-4115-4041-a0cc-3ae972102d2c

📥 Commits

Reviewing files that changed from the base of the PR and between e452112 and 7969a8c.

📒 Files selected for processing (4)
  • packages/opencode/src/cli/cmd/run/footer.ts
  • packages/opencode/src/cli/cmd/run/theme.ts
  • packages/opencode/test/cli/run/theme.test.ts
  • packages/tui/src/terminal-detection.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The TUI now detects terminal mode from OSC responses, COLORFGBG, and macOS appearance. Startup and direct-run fallback themes use the resolved mode. Fallback themes are generated lazily and cached per mode.

Changes

Terminal theme detection

Layer / File(s) Summary
Mode detection and resolution
packages/tui/src/terminal-detection.ts, packages/tui/test/terminal-detection.test.ts
Added COLORFGBG parsing, signal precedence, macOS appearance detection, and tests for success, failure, timeout, SSH, and CI cases.
Startup detection integration
packages/tui/src/app.tsx, packages/tui/package.json
Startup mode resolution combines OSC, COLORFGBG, and system appearance. Export mappings are reordered without changing their targets.
Mode-specific fallback themes
packages/opencode/src/cli/cmd/run/theme.ts, packages/opencode/src/cli/cmd/run/footer.ts, packages/opencode/test/cli/run/theme.test.ts
Fallback themes use mode-specific seeds, cache each mode lazily, and select the detected mode when theme resolution fails or lacks a background. Footer refresh logic and tests support both fallback instances.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 7969a

This change improves terminal theme selection and light-mode fallback behavior without introducing new interfaces, permissions, dependencies, or deployment behavior; no actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant TUI
  participant Terminal
  participant SystemAppearance
  participant ThemeResolver
  participant RunFooter
  TUI->>Terminal: Query OSC 11 and read COLORFGBG
  TUI->>SystemAppearance: Query appearance if terminal signals are absent
  SystemAppearance-->>TUI: Return light, dark, or unknown
  TUI->>ThemeResolver: Resolve initial mode
  ThemeResolver-->>TUI: Return mode-specific fallback theme
  ThemeResolver->>RunFooter: Provide resolved theme
  RunFooter->>RunFooter: Skip recognized fallback themes during palette refresh
Loading

Suggested reviewers: anandgupta42

Poem

A rabbit reads the terminal glow
Light seeds bloom and dark seeds show
OSC whispers, colors guide
Cached themes wait on either side
The TUI starts with mode in sight

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The theme detection and fallback changes are in scope for [#736]. However, reordering unchanged export mappings in packages/tui/package.json is unrelated to the issue and provides no functional change… Remove the packages/tui/package.json export reordering, or document a concrete requirement that makes this change necessary for the terminal theme fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: resolving terminal theme mode from available signals instead of defaulting to dark.
Description check ✅ Passed The description follows the required template. It identifies issue #736, marks the change as a bug fix, explains the implementation, documents verification, addresses screenshots, and completes the ch…
Linked Issues check ✅ Passed The changes address issue [#736] by adding ordered terminal theme detection, macOS appearance fallback, and mode-specific direct-run fallbacks for light terminals.
Full details: Description check

Explanation

The description follows the required template. It identifies issue #736, marks the change as a bug fix, explains the implementation, documents verification, addresses screenshots, and completes the checklist.

Full details: Out of Scope Changes check

Explanation

The theme detection and fallback changes are in scope for [#736]. However, reordering unchanged export mappings in packages/tui/package.json is unrelated to the issue and provides no functional change.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tui-terminal-readability

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

* This is the signal that was missing: every report of this bug came from
* darwin, on a terminal that answers neither of the cheaper probes.
*/
/** Minimal shape of `child_process.execFile`, injectable so tests can drive every branch. */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The detectSystemAppearance doc comment (lines 61-68) is now orphaned — inserting ExecFileLike here detaches it from the function it documents.

The macOS-specific explanation (AppleInterfaceStyle set to "Dark" in dark mode and absent in light mode, non-zero exit = light) is meant to describe detectSystemAppearance, but it now sits directly above the ExecFileLike type. Move that doc comment down to immediately precede detectSystemAppearance (or fold it into that function's JSDoc), so the two descriptions stop pointing at the wrong declarations.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread packages/tui/src/terminal-detection.ts Outdated
// silently reported as light.
const err = error as NodeJS.ErrnoException & { killed?: boolean; status?: number | null; stderr?: string }
const notFound = /does not exist/i.test(String(err.stderr ?? err.message ?? ""))
const clean = err.code === undefined && err.killed !== true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The clean clause is dead code that contradicts the contract stated in the comment above it.

For a real execFile error, code is always populated: the errno string on spawn failure (ENOENT/EACCES/EMFILE/...), the numeric exit code on a non-zero exit, or null when killed is true. So err.code === undefined never holds, and this clause never fires. If it ever did fire (e.g. a future error shape without code), it would silently report "light" for an unknown failure — exactly what the comment says must not happen. If the intent is to treat a clean non-zero exit (missing key) as light, match the exit status (typeof err.code === "number" / err.status === 1) instead; otherwise remove the clause.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

* the direct-run and scrollback renderer.
*/
function fallbackMode(renderer: CliRenderer): "dark" | "light" {
return resolveInitialMode({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: fallbackMode omits the OS-appearance signal, so the direct-run renderer does not actually "agree" with the TUI startup path it shares resolveInitialMode with.

The startup path feeds appearance from detectSystemAppearance() into resolveInitialMode, but this fallback only passes COLORFGBG and themeMode. On a light Apple Terminal (no COLORFGBG, no OSC 11 reply) a failed palette query still resolves to "dark" and reproduces the dark-on-dark symptom (#809) this PR targets. Consider threading appearance through here too (which would require making this path async), or note the limitation explicitly.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/cli/cmd/run/theme.ts 731 detectSystemAppearance() runs on the footer's palette-refresh failure path where the result is discarded
Files Reviewed (4 files)
  • packages/opencode/src/cli/cmd/run/footer.ts - 0 issues
  • packages/opencode/src/cli/cmd/run/theme.ts - 1 issue
  • packages/opencode/test/cli/run/theme.test.ts - 0 issues
  • packages/tui/src/terminal-detection.ts - 0 issues

Fix these issues in Kilo Cloud

Previous Review Summaries (2 snapshots, latest commit e452112)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit e452112)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/tui/src/terminal-detection.ts 69 Orphaned detectSystemAppearance doc comment detached by inserted ExecFileLike type
packages/tui/src/terminal-detection.ts 112 clean clause is dead code contradicting the documented failure-classification contract
packages/opencode/src/cli/cmd/run/theme.ts 703 fallbackMode omits OS appearance, so direct-run still falls back to dark on light Apple Terminal
Files Reviewed (6 files)
  • packages/opencode/src/cli/cmd/run/theme.ts - 1 issue
  • packages/opencode/test/cli/run/theme.test.ts - 0 issues
  • packages/tui/package.json - 0 issues
  • packages/tui/src/app.tsx - 0 issues
  • packages/tui/src/terminal-detection.ts - 2 issues
  • packages/tui/test/terminal-detection.test.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 0306bf0)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/tui/src/terminal-detection.ts 69 Orphaned detectSystemAppearance doc comment detached by inserted ExecFileLike type
packages/tui/src/terminal-detection.ts 112 clean clause is dead code contradicting the documented failure-classification contract
packages/opencode/src/cli/cmd/run/theme.ts 696 fallbackMode omits OS appearance, so direct-run still falls back to dark on light Apple Terminal
Files Reviewed (6 files)
  • packages/opencode/src/cli/cmd/run/theme.ts - 1 issue
  • packages/opencode/test/cli/run/theme.test.ts - 0 issues
  • packages/tui/package.json - 0 issues
  • packages/tui/src/app.tsx - 0 issues
  • packages/tui/src/terminal-detection.ts - 2 issues
  • packages/tui/test/terminal-detection.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4-pro · Input: 55.9K · Output: 36.6K · Cached: 828.2K

Review guidance: REVIEW.md from base branch main

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/tui/src/app.tsx">

<violation number="1" location="packages/tui/src/app.tsx:278">
P2: When a valid but stale `COLORFGBG` is present and OSC 11 takes longer than 250 ms, the live terminal answer is discarded and the stale value determines the theme. Keep the OSC probe alive through the full response deadline before falling back to `COLORFGBG`.</violation>
</file>

<file name="packages/opencode/src/cli/cmd/run/theme.ts">

<violation number="1" location="packages/opencode/src/cli/cmd/run/theme.ts:709">
P2: When a runtime palette refresh fails in light mode, this returns a distinct light fallback that `footer.ts` does not recognize as a fallback. The footer then replaces the last known-good theme; preserve the existing theme for either per-mode fallback.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/cli/cmd/run/theme.ts Outdated
Comment thread packages/tui/src/app.tsx
// now. COLORFGBG only buys a shorter wait: with a usable hint in hand we
// can stop waiting sooner, which keeps #704's startup win without
// letting a stale env var override a live answer.
const oscMode = (await renderer.waitForThemeMode(envMode ? 250 : 1000)) ?? null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a valid but stale COLORFGBG is present and OSC 11 takes longer than 250 ms, the live terminal answer is discarded and the stale value determines the theme. Keep the OSC probe alive through the full response deadline before falling back to COLORFGBG.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/tui/src/app.tsx, line 278:

<comment>When a valid but stale `COLORFGBG` is present and OSC 11 takes longer than 250 ms, the live terminal answer is discarded and the stale value determines the theme. Keep the OSC probe alive through the full response deadline before falling back to `COLORFGBG`.</comment>

<file context>
@@ -265,9 +265,19 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
+        // now. COLORFGBG only buys a shorter wait: with a usable hint in hand we
+        // can stop waiting sooner, which keeps #704's startup win without
+        // letting a stale env var override a live answer.
+        const oscMode = (await renderer.waitForThemeMode(envMode ? 250 : 1000)) ?? null
+        const appearance = oscMode || envMode ? null : await detectSystemAppearance()
+        const mode = resolveInitialMode({ colorfgbg: process.env.COLORFGBG, osc: oscMode, appearance })
</file context>
Suggested change
const oscMode = (await renderer.waitForThemeMode(envMode ? 250 : 1000)) ?? null
const oscMode = (await renderer.waitForThemeMode(1000)) ?? null

const bg = colors.defaultBackground ?? colors.palette[0]
if (!bg) {
return RUN_THEME_FALLBACK
return runThemeFallback(fallbackMode(renderer))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a runtime palette refresh fails in light mode, this returns a distinct light fallback that footer.ts does not recognize as a fallback. The footer then replaces the last known-good theme; preserve the existing theme for either per-mode fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/cli/cmd/run/theme.ts, line 709:

<comment>When a runtime palette refresh fails in light mode, this returns a distinct light fallback that `footer.ts` does not recognize as a fallback. The footer then replaces the last known-good theme; preserve the existing theme for either per-mode fallback.</comment>

<file context>
@@ -660,7 +706,7 @@ export async function resolveRunTheme(renderer: CliRenderer): Promise<RunTheme>
     const bg = colors.defaultBackground ?? colors.palette[0]
     if (!bg) {
-      return RUN_THEME_FALLBACK
+      return runThemeFallback(fallbackMode(renderer))
     }
 
</file context>

Comment thread packages/opencode/src/cli/cmd/run/theme.ts Outdated
Comment thread packages/tui/src/terminal-detection.ts Outdated
Marker Guard failed on #1152: theme.ts is an upstream-shared file, so custom
code there must be fenced to survive an upstream merge overwriting it. The
mode-aware fallback added in this branch was unmarked.

Six regions are now fenced: the shared-resolver import, the per-mode seed, the
memoized per-mode fallback theme, the mode probe, and both failure exits in
resolveRunTheme.

Verified with the same command CI runs:
  bun run script/upstream/analyze.ts --markers --base origin/main --strict

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

2 similar comments
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

Addresses the open review findings on this PR.

`fallbackMode` fed `resolveInitialMode` only COLORFGBG and the OSC
reply, so the direct-run renderer did not actually agree with the
startup path it shares that function with. On a light Apple Terminal —
no COLORFGBG, no OSC 11 answer — a failed palette query still resolved
to "dark" and repainted a light terminal dark, which is the #809
symptom this branch exists to remove. It now consults
`detectSystemAppearance()`, but only when both cheap signals came back
empty, so the `defaults` spawn stays off the common path.

`footer.ts` keeps the last known-good theme when a runtime palette
refresh fails, and detected that by comparing against
`RUN_THEME_FALLBACK`. Once the fallback became per-mode, a light
terminal produced a different instance, the identity check missed, and
the footer replaced a good theme with the fallback. `isRunThemeFallback`
tests membership in the memo map instead.

`detectSystemAppearance` had a `clean` clause resolving "light" for an
error with no `code` that was not killed. execFile never produces that
shape — it sets `code` to the errno string on spawn failure and to the
exit status otherwise — and had it fired it would have reported light
for an unknown failure, which the comment directly above it forbids.
Unknown now stays null so the caller can keep looking.

The palette-failure test pinned the dark instance by identity, which
re-encoded the behaviour being fixed and fails on a light-mode runner.
It now asserts that a failed lookup yields *a* fallback, with a separate
case keeping the dark instance pinned for an explicit dark signal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fJ3X7pcGT4R9yzjsJnqsV
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/test/cli/run/theme.test.ts">

<violation number="1" location="packages/opencode/test/cli/run/theme.test.ts:79">
P3: This new test duplicates an existing test already in this file: both are titled "a dark terminal still gets the dark fallback" and both assert that `resolveRunTheme(renderer({ fail: true, themeMode: "dark" }))` returns the dark fallback instance. Since `RUN_THEME_FALLBACK === runThemeFallback("dark")` (memoized identity), they are identical checks, and the same-named test at line 101 runs the same assertion. Duplicate registered test names are confusing and add no coverage. Drop one of the two (or merge them into a single dark-signal assertion).</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

expect(isRunThemeFallback(theme)).toBe(true)
})

test("a dark terminal still gets the dark fallback", async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This new test duplicates an existing test already in this file: both are titled "a dark terminal still gets the dark fallback" and both assert that resolveRunTheme(renderer({ fail: true, themeMode: "dark" })) returns the dark fallback instance. Since RUN_THEME_FALLBACK === runThemeFallback("dark") (memoized identity), they are identical checks, and the same-named test at line 101 runs the same assertion. Duplicate registered test names are confusing and add no coverage. Drop one of the two (or merge them into a single dark-signal assertion).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/test/cli/run/theme.test.ts, line 79:

<comment>This new test duplicates an existing test already in this file: both are titled "a dark terminal still gets the dark fallback" and both assert that `resolveRunTheme(renderer({ fail: true, themeMode: "dark" }))` returns the dark fallback instance. Since `RUN_THEME_FALLBACK === runThemeFallback("dark")` (memoized identity), they are identical checks, and the same-named test at line 101 runs the same assertion. Duplicate registered test names are confusing and add no coverage. Drop one of the two (or merge them into a single dark-signal assertion).</comment>

<file context>
@@ -60,7 +66,21 @@ function spread(color: RGBA) {
+  expect(isRunThemeFallback(theme)).toBe(true)
+})
+
+test("a dark terminal still gets the dark fallback", async () => {
+  // The mode-aware path must not have inverted anything: given an explicit
+  // dark signal the fallback is still the dark instance callers compare by
</file context>

// OSC 11 reply — it still resolved "dark" and repainted a light terminal
// dark, which is the #809 symptom this change exists to remove. The probe
// spawns `defaults`, so it stays behind the two free signals.
const appearance = osc || detectModeFromCOLORFGBG(colorfgbg) ? null : await detectSystemAppearance()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: detectSystemAppearance() spawns defaults on the footer's palette-refresh failure path, where the answer is discarded.

resolveRunTheme is shared by the direct-run startup (runtime.lifecycle.ts:198, which consumes the fallback) and the TUI footer's handlePalette (footer.ts:1009). In the footer, isRunThemeFallback(theme) discards the fallback to keep the last-known-good theme, so the OS probe's result is thrown away. On the exact machine this PR targets — a light macOS Apple Terminal with no COLORFGBG and no OSC 11 reply — every failed runtime palette refresh now spawns /usr/bin/defaults and waits up to 400ms for nothing. Only the direct-run path consumes the appearance signal; consider skipping the probe when the fallback will be discarded.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code output still renders as white text on light terminal backgrounds (regression of #704)

1 participant